# 无监督学习
# 聚类是无监督，分类是有监督

# sklearn中聚类算法有两种表现形式。
# 一、类，需要实例化、训练并使用接口和树形来调用结果。
# 二、函数，只需要输入特征矩阵和超参数，街客返回聚类的结果和各种指标。

# Kmeans
# 将一组N个样本的特征矩阵X划分未K个无交集的簇。
# 过程
# 1. 随机抽取K个样本作为最初的质心
# 2. 开始循环：
# 2.1 将每个样本点分配到离他们最近的质心，生成K个簇
# 2.2 对于每个簇，计算所有被分到该簇的样本点的平均值作为新的质心
# 3 当质心的位置不再发生变化，迭代停止，聚类完成

# 认为，被分在同一个簇中的数据是有相似性的，二不同簇中的数据是不同的。
# 我们追求“簇内查一下哦，簇外差异大”。这个差异，由样本点到其所在簇的之心的距离来衡量。
# 欧几里得距离、曼哈顿距离、余弦距离。在sklearn中只能使用欧式距离。
# 一个数据集内所有簇的簇内平方和相加，就得到整体平方和，又称为Inertia。KMeans追求的是，求解能够让Inertia最小的质心。
# 整体平方和Inertia和逻辑回归中的损失函数的功能非常相似。
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
X, y = make_blobs(n_samples=500
                  ,n_features=2     # 特征数
                  ,centers=5        # 中心
                  ,random_state=3
                  )
# 可视化
# fig, ax1 = plt.subplots(1)
# ax1.scatter(X[:, 0], X[:, 1]
#            ,marker='o'
#            ,s=8
#            )
# plt.show()

n_clusters = 5
cluster = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
y_pred = cluster.labels_        # 簇的编号
# print(y_pred)
centroid = cluster.cluster_centers_     # 三个簇心的特征值
print(centroid)
inertia = cluster.inertia_      # 簇的整体平方和
print(inertia)


# color = ["red","pink","orange","gray","yellow"]
# fig, ax1 = plt.subplots(1)
# for i in range(n_clusters):
#     ax1.scatter(X[y==i, 0], X[y==i, 1]
#            ,marker='o'
#            ,s=8
#            ,c=color[i]
#            )
# ax1.scatter(centroid[:,0],centroid[:,1]
#            ,marker="x"
#            ,s=15
#            ,c="black")
# plt.show()

# 聚类算法的模型评估指标
# 对不知道真正答案的数据进行聚类时，要完全依赖于评价簇内的稠密程度（簇内差异小）和簇间的离散程度（簇外差异大）来评估聚类的效果。
# 轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的，它能够同时衡量：
# 1. 样本与其自身所在的簇中的其他样本的相似度a，等于样本与同一簇中所有其他点之间的平均距离
# 2. 样本与其他簇中的样本的相似度b，等于样本与下一个最近的簇中得所有点之间的平均距离
# 根据聚类的要求”簇内差异小，簇外差异大“，我们希望b永远大于a，并且大得越多越好。

# 调整n_clusters让silhouette_samples的值更接近1。但最佳簇数的轮廓系数不一定随最高的
# n_clusters的数量要根据业务决定，不一定是silhouette_samples最高时的簇数
# inertia会随着n_clusters的增多而增多，但silhouette_samples有最高值


from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples
print(silhouette_score(X,y_pred))
print(silhouette_samples(X,y_pred))

# 轮廓系数的缺陷：
# 它在凸型的类上表现会虚高，比如基于密度进行的聚类，或通过DBSCAN获得的聚类结果，如果使用轮廓系数来衡量，则会表现出比真实聚类效果更高的分数


